/******************************************************************************
* 	Copyright (c) 2007-2008 All rights reserved
*		Asemantics S.r.l
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* 1. Redistributions of source code must retain the above copyright
*    notice, this list of conditions and the following disclaimer. 
*
* 2. Redistributions in binary form must reproduce the above copyright
*    notice, this list of conditions and the following disclaimer in
*    the documentation and/or other materials provided with the
*    distribution.
*
* 3. The end-user documentation included with the redistribution,
*    if any, must include the following acknowledgment:
*       "This product includes software developed by 
*	 Asemantics S.r.l."
*    Alternately, this acknowledgment may appear in the software itself,
*    if and wherever such third-party acknowledgments normally appear.
*
* 4. All advertising materials mentioning features or use of this software
*    must display the following acknowledgement:
*    This product includes software developed by Asemantics S.r.l.
*    the Semantic Web company, Rome, London, Leiden and its contributors. 
*
* 5. Neither the name of the company nor the names of its contributors
*    may be used to endorse or promote products derived from this software
*    without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
* ANY EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
* OF THE POSSIBILITY OF SUCH DAMAGE.
*
* Author: Andrea Marchesini - baku@asemantics.com
*
******************************************************************************/

#include <controller.h>
#include <iostring.h>

#include <string.h>
#include <glib.h>

#define INTERNAL_MAXSIZE 1024 * 8

static TBIOString *io_string_new_ro (TBIOString * parent);
static gboolean io_string_swap_new (TBIOString * string, GError ** error);
static gboolean io_string_swap_write (TBIOString * string, gchar * buffer,
				      guint64 size, GError ** error);
static gboolean io_string_swap_write_real (TBIOString * string,
					   TBSwapFile * file, gchar * buffer,
					   guint64 size, GError ** error);
static gchar *io_string_to_gchar_rw (TBIOString * string, guint64 offset,
				     gsize * size);
static gchar *io_string_to_gchar_ro (TBIOString * string, guint64 offset,
				     gsize * size);
static TBSwap *io_string_get_swap (TBIOString * string);
static gint io_string_swap_id (TBSwap * swap);

TBIOString *
io_string_new (gchar * string, gint64 size, TBSwap * swap, GError ** error)
{
  TBIOString *new;

  new = g_malloc0 (sizeof (TBIOString));
  size = size < 0 ? strlen (string) : size;

  new->is_rw = TRUE;
  new->rw.swap = swap;

  if (size < swap->size)
    {
      new->rw.string = g_string_new (string);
      new->size = size;
    }

  else
    {
      if (io_string_swap_new (new, error) == FALSE
	  || io_string_append (new, string, size, error) == FALSE)
	{
	  io_string_ref (new);
	  io_string_unref (new);
	  return NULL;
	}
    }

  io_string_ref (new);
  return new;
}

TBIOString *
io_string_new_empty (TBSwap * swap, GError ** error)
{
  return io_string_new ("", 0, swap, error);
}

static TBIOString *
io_string_new_ro (TBIOString * parent)
{
  TBIOString *new;

  new = g_malloc0 (sizeof (TBIOString));

  new->is_rw = FALSE;
  new->ro.string = parent;
  new->ro.offset = 0;

  io_string_ref (parent);
  io_string_ref (new);

  return new;
}

TBIOString *
io_string_trim (TBIOString * string, GError ** error)
{
  TBIOString *new;
  gchar *buffer;
  gsize size;

  TBSwap *swap;

  guint64 offset = 0;

  new = io_string_new_ro (string);
  new->size = string->size;

  /* The begin: */
  while ((buffer = io_string_to_gchar (string, offset, &size)))
    {
      gchar *tmp;

      offset += size;
      tmp = buffer;

      while (g_ascii_isspace (*tmp) == TRUE)
	{
	  tmp++;
	  size--;

	  new->ro.offset++;
	  new->size--;
	}

      g_free (buffer);

      if (size > 0)
	break;
    }

  /* The tail: */
  swap = io_string_get_swap (string);

  if (string->size > swap->size)
    offset = swap->size;
  else
    offset = 0;

  do
    {
      if (!(buffer = io_string_to_gchar (new, offset, &size)))
	break;

      if (offset > size)
	offset -= size;
      else
	offset = 0;

      size--;

      while (size >= 0 && g_ascii_isspace (buffer[size]) == TRUE)
	{
	  size--;
	  new->size--;
	}

      g_free (buffer);

      if (size >= 0)
	break;
    }
  while (offset > 0);

  return new;
}

void
io_string_ref (TBIOString * string)
{
  if (!string)
    return;

  string->ref++;
}

void
io_string_unref (TBIOString * string)
{
  if (!string)
    return;

  string->ref--;

  if (string->ref)
    return;

  /* ReadOnly: */
  if (string->is_rw == FALSE)
    {
      io_string_unref (string->ro.string);
    }

  /* ReadWrite: */
  else
    {
      if (string->rw.string)
	g_string_free (string->rw.string, TRUE);

      if (string->rw.swapfiles)
	{
	  GList *list;

	  for (list = string->rw.swapfiles; list; list = list->next)
	    {
	      TBSwapFile *file = list->data;

	      if (file->io)
		{
		  g_io_channel_shutdown (file->io, TRUE, NULL);
		  g_io_channel_unref (file->io);
		}

	      if (file->file)
		{
		  g_unlink (file->file);
		  g_free (file->file);
		}

	      g_free (file);
	    }

	  g_list_free (string->rw.swapfiles);
	}
    }

  g_free (string);
}


static gboolean
io_string_swap_new (TBIOString * string, GError ** error)
{
  gchar *buffer;
  TBSwapFile *file;

  if (string->is_rw == FALSE)
    {
      g_assert ("Internal error!");
      return FALSE;
    }

  file = g_malloc0 (sizeof (TBSwapFile));

  buffer = g_strdup_printf ("%d", io_string_swap_id (string->rw.swap));

  file->file =
    g_build_path (G_DIR_SEPARATOR_S, string->rw.swap->directory, buffer,
		  NULL);

  g_free (buffer);

  string->rw.swapfiles = g_list_append (string->rw.swapfiles, file);

  if (!(file->io = g_io_channel_new_file (file->file, "w+", error)))
    return FALSE;

  if (g_io_channel_set_encoding (file->io, NULL, error) == FALSE)
    return FALSE;

  if (string->rw.string)
    {
      guint64 tmp = string->size;

      string->size = 0;

      if (tmp
	  && io_string_swap_write (string, string->rw.string->str, tmp,
				   error) == FALSE)
	return FALSE;

      g_string_free (string->rw.string, TRUE);
      string->rw.string = NULL;
      string->size = tmp;
    }

  return TRUE;
}

static gboolean
io_string_swap_write (TBIOString * string, gchar * buffer, guint64 size,
		      GError ** error)
{
  guint64 todo;
  TBSwapFile *file;

  if (string->is_rw == FALSE)
    {
      g_assert ("Internal error!");
      return FALSE;
    }

  file = g_list_last (string->rw.swapfiles)->data;

  if ((string->rw.swap->filesize - file->size) >= size)
    return io_string_swap_write_real (string, file, buffer, size, error);

  todo = string->rw.swap->filesize - file->size;
  if (io_string_swap_write_real (string, file, buffer, todo, error) == FALSE)
    return FALSE;

  buffer += todo;
  size -= todo;

  while (size > 0)
    {
      if (io_string_swap_new (string, error) == FALSE)
	return FALSE;

      file = g_list_last (string->rw.swapfiles)->data;

      if (size <= string->rw.swap->filesize)
	todo = size;
      else
	todo = string->rw.swap->filesize;

      if (io_string_swap_write_real (string, file, buffer, todo, error) ==
	  FALSE)
	return FALSE;

      buffer += todo;
      size -= todo;
    }

  return TRUE;
}

static gboolean
io_string_swap_write_real (TBIOString * string, TBSwapFile * file,
			   gchar * buffer, guint64 size, GError ** error)
{
  guint64 done;

  if (file->offset != file->size
      && g_io_channel_seek_position (file->io, file->size, G_SEEK_SET,
				     error) == FALSE)
    return FALSE;

  done = 0;
  while (done < size)
    {
      GIOStatus status;
      gsize todo, written;

      /* Max Size convertion: */
      if ((size - done) > G_MAXSIZE)
	todo = G_MAXSIZE;
      else
	todo = size - done;

      status =
	g_io_channel_write_chars (file->io, buffer + done, todo, &written,
				  error);

      if (status == G_IO_STATUS_ERROR)
	return FALSE;

      if (status == G_IO_STATUS_AGAIN)
	continue;

      done += written;
    }

  file->size += done;
  file->offset = file->size;
  g_io_channel_flush (file->io, NULL);
  return TRUE;
}

gboolean
io_string_append (TBIOString * string, gchar * what, gint64 size,
		  GError ** error)
{
  if (string->is_rw == FALSE)
    {
      g_assert ("Internal error!");
      return FALSE;
    }

  size = size >= 0 ? size : strlen (what);

  if (string->rw.swapfiles)
    {
      gboolean ret;
      ret = io_string_swap_write (string, what, size, error);
      string->size += size;
      return ret;
    }

  if (size < (string->rw.swap->size - string->size))
    {
      string->rw.string = g_string_append_len (string->rw.string, what, size);
      string->size += size;
      return TRUE;
    }

  if (io_string_swap_new (string, error) == FALSE)
    return FALSE;

  return io_string_append (string, what, size, error);
}

/* This function returns a string from the iostring.
 * The length will be the swap->size: */
gchar *
io_string_to_gchar (TBIOString * string, guint64 offset, gsize * size)
{
  if (!string)
    return NULL;

  if (string->is_rw == TRUE)
    return io_string_to_gchar_rw (string, offset, size);
  else
    return io_string_to_gchar_ro (string, offset, size);
}

static gchar *
io_string_to_gchar_rw (TBIOString * string, guint64 offset, gsize * size)
{
  gsize tr, done, todo;
  gchar *ret;

  GList *list;

  TBSwapFile *file;

  if ((string->size - offset) < string->rw.swap->size)
    tr = string->size - offset;
  else
    tr = string->rw.swap->size;

  if (!tr)
    return NULL;

  /* Max size: */
  if (tr > INTERNAL_MAXSIZE)
    tr = INTERNAL_MAXSIZE;

  /* Not swapped yet: */
  if (string->rw.string)
    {
      if (size)
	*size = tr;

      return g_strndup (string->rw.string->str + offset, tr);
    }

  /* Seeking: */
  done = offset / string->rw.swap->filesize;

  if (!(list = g_list_nth (string->rw.swapfiles, done)))
    return NULL;

  file = list->data;
  offset = offset % string->rw.swap->filesize;

  if (file->offset != offset)
    {
      if (g_io_channel_seek_position (file->io, offset, G_SEEK_SET, NULL) ==
	  FALSE)
	return NULL;

      file->offset = offset;
    }

  /* Reading: */
  ret = g_malloc (sizeof (gchar) * (tr + 1));
  done = 0;

  while (done < tr)
    {
      GIOStatus status;
      gsize read;

      if ((file->size - file->offset) < (tr - done))
	todo = file->size - file->offset;
      else
	todo = tr - done;

      status =
	g_io_channel_read_chars (file->io, ret + done, todo, &read, NULL);

      if (status == G_IO_STATUS_EOF)
	break;

      switch (status)
	{
	case G_IO_STATUS_ERROR:
	  g_free (ret);
	  return NULL;

	case G_IO_STATUS_NORMAL:
	  done += read;
	  file->offset += read;

	  if (file->offset == file->size)
	    {
	      list = list->next;

	      if (list)
		{
		  file = list->data;

		  if (file->offset != 0)
		    {
		      if (g_io_channel_seek_position
			  (file->io, 0, G_SEEK_SET, NULL) == FALSE)
			{
			  g_free (ret);
			  return NULL;
			}

		      file->offset = 0;
		    }
		}
	    }

	  break;

	default:
	  break;
	}

      if (!list)
	break;
    }

  if (size)
    *size = done;

  ret[done] = 0;
  return ret;
}

static gchar *
io_string_to_gchar_ro (TBIOString * string, guint64 offset, gsize * size)
{
  gchar *output;
  gsize s, real, last;

  if (offset >= string->size)
    return NULL;

  if (!
      (output =
       io_string_to_gchar (string->ro.string, offset + string->ro.offset,
			   &s)))
    return NULL;

  real = string->size - offset;

  if (s > real)
    last = real;
  else
    last = s;

  if (size)
    *size = last;

  output[last] = 0;

  return output;
}

/* This function splits a TBIOString in a list by an handler: */
gboolean
io_string_split (TBIOString * string, gchar * split, TBIOStringSplitFunc func,
		 gpointer func_data, GError ** error)
{
  gchar *buffer, *old;
  gsize size;
  guint64 old_size, offset, coffset;
  guint64 split_len;
  guint64 i, to;

  TBIOString *current;

  offset = coffset = 0;

  old = NULL;
  old_size = 0;

  split_len = strlen (split);

  current = io_string_new_ro (string);

  while ((buffer = io_string_to_gchar (string, offset, &size)))
    {
      gchar *tmp;
      guint64 tmp_size;
      gboolean allocated;

      offset += size;

      if (!old)
	{
	  allocated = FALSE;
	  tmp = buffer;
	  tmp_size = size;
	}
      else
	{
	  allocated = TRUE;

	  tmp = g_malloc (sizeof (gchar) * (old_size + size));

	  memcpy (tmp, old, old_size);
	  memcpy (tmp + old_size, buffer, size);

	  tmp_size = old_size + size;

	  g_free (old);
	  old = NULL;
	}

      if (tmp_size < split_len)
	{
	  old = tmp;
	  old_size = tmp_size;

	  if (allocated == TRUE)
	    g_free (buffer);
	  continue;
	}

      to = i = 0;

      while (i < tmp_size - split_len)
	{
	  if (!strncmp (tmp + i, split, split_len))
	    {
	      current->size += i - to;

	      /* Function: */
	      func (current, func_data);

	      coffset += current->size + split_len;
	      io_string_unref (current);

	      current = io_string_new_ro (string);
	      current->ro.offset = coffset;

	      i += split_len;
	      to = i;
	      continue;
	    }

	  i++;
	}

      if (i != to)
	current->size += i - to;

      old = g_malloc (sizeof (gchar) * tmp_size - i);
      memcpy (old, tmp + i, tmp_size - i);
      old_size = tmp_size - i;

      if (allocated)
	g_free (tmp);

      g_free (buffer);
    }

  if (old)
    {
      current->size += old_size;
      g_free (old);
    }

  /* Function: */
  func (current, func_data);
  io_string_unref (current);

  return TRUE;
}

/* This function returns the size of a TBIOString: */
guint64
io_string_get_size (TBIOString * string)
{
  if (!string)
    return 0;

  return string->size;
}

/* How to the swap pointer: */
static TBSwap *
io_string_get_swap (TBIOString * string)
{
  if (string->is_rw)
    return string->rw.swap;

  return io_string_get_swap (string->ro.string);
}

static gint
io_string_swap_id (TBSwap * swap)
{
  gint id;

  g_mutex_lock (swap->mutex);
  id = swap->id;
  swap->id++;
  g_mutex_unlock (swap->mutex);

  return id;
}

/* EOF */
